/* igrs.c */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "igrs.h"
#include "http.h"
#include "dump.h"

#define REQ_NUM_VERSION  0
#define REQ_NUM_MSG_TYPE 1
#define REQ_NUM_DEV_ID   2
#define REQ_NUM_SEQ_ID   3
#define REQ_NUM_CLI_ID   4
#define REQ_NUM_LISTENER 5
#define REQ_NUM_END      6

#define REQ_FLG_VERSION   (1<<REQ_NUM_VERSION)
#define REQ_FLG_MSG_TYPE  (1<<REQ_NUM_MSG_TYPE)
#define REQ_FLG_DEV_ID    (1<<REQ_NUM_DEV_ID)
#define REQ_FLG_SEQ_ID    (1<<REQ_NUM_SEQ_ID)
#define REQ_FLG_CLI_ID    (1<<REQ_NUM_CLI_ID)
#define REQ_FLG_LISTENER  (1<<REQ_NUM_LISTENER)
#define REQ_FLG_ALL       ((1<<REQ_NUM_END)-1)

#define REP_NONE                  0
#define REP_DEV_ID                1
#define REP_CLI_ID                2
#define REP_LISTENER_LIST         3
#define REP_SECURE_LISTENER_LIST  4
#define REP_CONTENT_LENGTH        5
#define REP_ACK_ID                6

#define REP_FIELD2(n) #n
#define REP_FIELD(n)  "\xff" REP_FIELD2(n) "\xff"

typedef struct REQUEST_INFO {
  unsigned int flags;
  char seq_id[16];
  char cli_id[16];
  char dev_id[128];
  char listener[128];
} REQUEST_INFO;

typedef struct REPLY_INFO {
  REQUEST_INFO *req;
  int content_length;
  char listener[64];
  char secure_listener[64];
} REPLY_INFO;

typedef struct REPLY_HEADER {
  char *header;
  char *value;
  int field;
} REPLY_HEADER;

static REPLY_HEADER reply_headers[] = {
  { "HTTP/1.1 200 OK" },
  { "EXT", "" },
  { "Cache-control", "no-cache=\"Ext\"" },
  { "SERVER", " Linux/2.6 IGRS/1.0 Lenovo IGRS Stack/1.01" },
  { "ST", "urn:schemas-IGRS-org:service:IGRS-service:1" },
  { "LOCATION", "http://www.igrs.org/device" },
  { "USN", "uuid:urn:IGRS:Device:DeviceId:00000000-0000-0000-0000-000000000000::urn:IGRS:Service:ServiceType:EKServer:1.0" },
  { "01-IGRSVersion", "IGRS/1.0" },
  { "01-IGRSMessageType", "SearchServiceResponse" },
  { "01-SourceDeviceId", "urn:IGRS:Device:DeviceId:00000000-0000-0000-0000-000000000000" },
  { "01-TargetDeviceId", NULL, REP_DEV_ID },
  { "01-DeviceSecurityIdList", "urn:IGRS:DeviceSecurity:NULL" },
  { "CONTENT-TYPE", "text/xml; charset=utf-8" },
  { "MAN", "\"http://www.igrs.org/spec1.0\";ns=01" },
  { "MAN", "\"http://schemas.xmlsoap.org/soap/envelope/\";ns=02" },
  { "02-SoapAction", "\"IGRS-SearchService-Response\"" },
  { "01-ListenerList", NULL, REP_LISTENER_LIST },
  { "01-SecureListenerList", NULL, REP_SECURE_LISTENER_LIST },
  { "Content-Length", NULL, REP_CONTENT_LENGTH },
  { NULL }
};

static char *reply_content[] = {
  "<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">",
  "<SOAP-ENV:Body>",
  "<DeviceOperation xmlns=\"http://www.igrs.org/spec1.0\">",
  "<AcknowledgeId>" REP_FIELD(REP_ACK_ID) "</AcknowledgeId>",
  "<ReturnCode>100</ReturnCode>",
  "<TargetClientId>" REP_FIELD(REP_CLI_ID) "</TargetClientId>",
  "<SearchResult>",
  "<ServiceInfoList>",
  "<ServiceInfo>",
  "<DeviceId>urn:IGRS:Device:DeviceId:00000000-0000-0000-0000-000000000000</DeviceId>",
  "<ServiceName>EKServer</ServiceName>",
  "<ServiceId>1234567</ServiceId>",
  "<ServiceType>urn:IGRS:Service:ServiceType:EKServer:1.0</ServiceType>",
  "<ServiceSecurityIdList/>",
  "<Location>http://www.igrs.org/service</Location>",
  "</ServiceInfo></ServiceInfoList></SearchResult>",
  "</DeviceOperation>",
  "</SOAP-ENV:Body>",
  "</SOAP-ENV:Envelope>",
  NULL,
};

static void copy_str(char *dest, char *src, int max_len)
{
  strncpy(dest, src, max_len-1);
  dest[max_len-1] = '\0';
}

static int get_request_header(char *header, char *value, void *data)
{
  REQUEST_INFO *req = (REQUEST_INFO *) data;

  if (value == NULL) {
    if (strcasecmp(header, "M-SEARCH * HTTP/1.1") != 0)
      return 1;
    return 0;
  }

  //printf("'%s' = '%s'\n", header, value);

  if (strcasecmp(header, "01-IGRSVersion") == 0) {
    req->flags |= REQ_FLG_VERSION;
    if (strcasecmp(value, "IGRS/1.0") != 0)
      return 1;
    return 0;
  }

  if (strcasecmp(header, "01-IGRSMessageType") == 0) {
    req->flags |= REQ_FLG_MSG_TYPE;
    if (strcasecmp(value, "SearchServiceRequest") != 0)
      return 1;
    return 0;
  }

  if (strcasecmp(header, "01-SourceDeviceId") == 0) {
    req->flags |= REQ_FLG_DEV_ID;
    copy_str(req->dev_id, value, sizeof(req->dev_id));
    return 0;
  }

  if (strcasecmp(header, "01-SequenceId") == 0) {
    req->flags |= REQ_FLG_SEQ_ID;
    copy_str(req->seq_id, value, sizeof(req->seq_id));
    return 0;
  }

  if (strcasecmp(header, "01-ClientId") == 0) {
    req->flags |= REQ_FLG_CLI_ID;
    copy_str(req->cli_id, value, sizeof(req->cli_id));
    return 0;
  }
  
  if (strcasecmp(header, "01-ListenerList") == 0) {
    req->flags |= REQ_FLG_LISTENER;
    copy_str(req->listener, value, sizeof(req->listener));
    return 0;
  }

  return 0;
}

static int extract_host_port(const char *str, net_u32 *ip, int *port)
{
  char host[128];
  char *p;
  int len;

  p = strchr(str, ':');
  if (! p)
    return -1;
  len = p - str;
  p++;
  if (len > sizeof(host)-1)
    len = sizeof(host)-1;
  strncpy(host, str, len);
  host[len] = '\0';
  if (ip_from_string(host, ip) != 0)
    return -1;
  *port = atoi(p);
  return 0;
}

static char *get_reply_field(int field_num, REPLY_INFO *rep)
{
  //printf("getting value of field %d\n", field_num);
  switch (field_num) {
  case REP_DEV_ID: return rep->req->dev_id;
  case REP_CLI_ID: return rep->req->cli_id;
  case REP_ACK_ID: return rep->req->seq_id;
  case REP_LISTENER_LIST: return rep->listener;
  case REP_SECURE_LISTENER_LIST: return rep->secure_listener;
  default: return NULL;
  }
}

static int build_reply_headers(char *buf, int max_len, REPLY_INFO *rep)
{
  REPLY_HEADER *h;
  int cur_len, i;

  /* header value field */
  cur_len = 0;
  for (i = 0; reply_headers[i].header != NULL; i++) {
    int len;
    h = &reply_headers[i];

    // header
    len = strlen(h->header);
    if (buf && ((max_len < 0 || cur_len + len <= max_len)))
      memcpy(buf + cur_len, h->header, len);
    cur_len += len;

    // value
    if (h->value != NULL) {
      len = strlen(h->value) + 1;
      if (buf && ((max_len < 0 || cur_len + len <= max_len))) {
	buf[cur_len] = ':';
	memcpy(buf + cur_len + 1, h->value, len);
      }
      cur_len += len;
    }

    // field
    if (h->field != REP_NONE) {
      char content_len[20], *val;

      if (h->field == REP_CONTENT_LENGTH) {
	sprintf(content_len, "%d", rep->content_length);
	val = content_len;
      } else
	val = get_reply_field(h->field, rep);
      len = strlen(val) + 1;
      if (buf && ((max_len < 0 || cur_len + len <= max_len))) {
	buf[cur_len] = ':';
	memcpy(buf + cur_len + 1, val, len);
      }
      cur_len += len;
    }

    // line separator ("\r\n")
    if (buf && (max_len < 0 || cur_len + 2 <= max_len)) {
      buf[cur_len] = '\r';
      buf[cur_len + 1] = '\n';
    }
    cur_len += 2;
  }

  // header end ("\r\n")
  if (buf && (max_len < 0 || cur_len + 2 <= max_len)) {
    buf[cur_len] = '\r';
    buf[cur_len + 1] = '\n';
  }
  cur_len += 2;

  return cur_len;
}

static int build_reply_content(char *buf, int max_len, REPLY_INFO *rep)
{
  int cur_len, i;

  cur_len = 0;
  for (i = 0; reply_content[i] != NULL; i++) {
    char *p = reply_content[i];

    /* fast version: no fields to replace */
    if (strchr(p, '\xff') == NULL) {
      int len = strlen(p);

      if (buf && (max_len < 0 || cur_len + len <= max_len))
	memcpy(buf + cur_len, p, len);
      cur_len += len;
      continue;
    }

    /* slow version: copy replacing values as needed */
    while (*p && (max_len < 0 || cur_len <= max_len)) {
      if (*p != '\xff') {
	if (buf)
	  buf[cur_len] = *p;
	cur_len++;
	p++;
	} else {
	char *field = get_reply_field(atoi(p+1), rep);
	int len = (field) ? strlen(field) : 0;
	p++;    // skip first \xff
	while (*p && *p != '\xff')
	  p++;  // skip field specifier
	p++;    // skip second \xff
	if (field != NULL) {
	  if (buf && (max_len < 0 || cur_len + len <= max_len))
	    memcpy(buf + cur_len, field, len);
	}
	cur_len += len;
      }
    }
  }

  return cur_len;
}

/**
 * Build a HTTP reply to an IGRS request.
 *
 * On success, a memory region is allocated and filled, and *reply is
 * set to point to it.  Callers of this function should free() it.  The
 * IP address and UDP port where the reply is to be sent are returned
 * in *ip and *port.
 *
 * Return the number of bytes in the reply, or -1 on error.
 */
int igrs_build_reply(char *request, int len, net_u32 listener_ip, int listener_port, char **reply, net_u32 *ip, int *port)
{
  REQUEST_INFO req;
  REPLY_INFO rep;
  int headers_len, content_len;
  char *reply_buf;

  *reply = NULL;

  /* get headers */
  memset(&req, 0, sizeof(REQUEST_INFO));
  if (http_extract_headers(request, len, NULL, get_request_header, &req) != 0)
    return -1;
  if (req.flags != REQ_FLG_ALL)
    return -1;

  /* get host and port from request */
  if (extract_host_port(req.listener, ip, port) != 0)
    return -1;
  
  /* set reply info */
  memset(&rep, 0, sizeof(REPLY_INFO));
  rep.req = &req;
  snprintf(rep.listener, sizeof(rep.listener), "%d.%d.%d.%d:%d",
	   (listener_ip>>24)&0xff, (listener_ip>>16)&0xff, (listener_ip>>8)&0xff, listener_ip&0xff,
	   listener_port);
  snprintf(rep.secure_listener, sizeof(rep.secure_listener), "%d.%d.%d.%d",
	   (listener_ip>>24)&0xff, (listener_ip>>16)&0xff, (listener_ip>>8)&0xff, listener_ip&0xff);

  /* calculate reply length */
  content_len = build_reply_content(NULL, -1, &rep);
  rep.content_length = content_len;
  headers_len = build_reply_headers(NULL, -1, &rep);

  //printf("headers len: %d\n", headers_len);
  //printf("content len: %d\n", content_len);

  /* build reply */
  reply_buf = malloc(headers_len + content_len);
  if (! reply_buf)
    return -1;
  build_reply_headers(reply_buf, headers_len, &rep);
  build_reply_content(reply_buf + headers_len, content_len, &rep);

  *reply = reply_buf;
  return headers_len + content_len;
}


